
#include "../../HashExtensible/src/HashExtensible.h"

#define FIN_LINEA 	'\n'
#define FIN_CADENA 	'\0'
#define ESP_BLANCO 	' '

#define TAMANIO_BLOQUE 				1024
#define ERROR_ARGS_INSUF			1
#define ERROR_FORMATO				2
#define ERROR_CREACION				3
#define ERROR_ARCHIVO_VACIO			4
#define ERROR_ARCHIVO_ARGUMENTOS	5
#define ERROR_OPERACION_INVALIDA	6
#define ERROR_CLAVE_NO_EXISTE		7

/******* DEFINICION DE FUNCIONES AUXILIARES DE INTERFAZ *******/


void mostrarError(int codigo);
int esOpcionValida(char* opcion);
void opcionHelp();
int opcionBuscar(char* cadena, HashExtensible* hash);
int opcionInsertar(char* cadena, HashExtensible* hash);
int opcionModificar(char* cadena, HashExtensible* hash);
int opcionQuitar(char* cadena, HashExtensible* hash);
int opcionTexto(HashExtensible* hash, enum TBOOL isFull, char* ruta);
int validarNumero(char numero);
int validarParentesisCierra(char argumento);
int validarParentesisAbre(char argumento);
int obtenerValor(char* cadena, char** valor, int* posicion);
int obtenerClave(char* cadena, char** clave, int* posicion);
void crearRutaArchivos(char* rutaTabla, char* rutaBloques, char* rutaBloquesLibres);
char* leerEntrada ();
char* obtenerArgumento (char* lista, int* posicion);
char * numeroToString(unsigned int numero);

/******* FIN DEFINICION DE FUNCIONES AUXILIARES DE INTERFAZ *******/


/******* MAIN *******/

int main( int argc, char** argv )
{

	//No ingreso ningun parametro
	if (argc == 1) {
		mostrarError(ERROR_ARGS_INSUF);
		return OK;
	}

	if((strcmp(argv[1], "-h") == 0))
	{
		opcionHelp();
		return OK;
	}

	if(argc<3){
		mostrarError(ERROR_ARGS_INSUF);
		return OK;
	}

	int codigoError = 0;
	int posicion = 0;
	char * nombreArchivo = argv[1];
	char * entrada; 		//contiene todos los argumentos levantados desde stdin
	char * actual; 			//argumento en proceso
	char * opcion = argv[2];//operacion

	if((strcmp(opcion, "-h") == 0))
	{
		opcionHelp();
		return OK;
	}

	HashExtensible * hash = CrearHashExtensible (TAMANIO_BLOQUE, nombreArchivo);
	if (hash == NULL)
	{
		mostrarError(ERROR_CREACION);
		return OK;
	}

	/*Si la opcion es valida, se la ejecuta*/
	if ((esOpcionValida(opcion)) && (opcion != NULL))
	{

		if (strcmp(opcion, "-S") == 0){
			char * ruta = (char*) malloc (sizeof(char) * 256);
			strcpy(ruta, "stdout");
			if(argc >= 4){
				if(strcmp(argv[3],"-FULL") == 0 && (argc == 4)){
					codigoError = opcionTexto(hash, TRUE, ruta); //es FULL
				}
				else if (argc >= 5)
					{
						if((strcmp(argv[3],"-FULL") == 0))
						{
							strcpy(ruta, argv[4]);
							codigoError = opcionTexto(hash, TRUE, ruta); //es FULL
						}
						else if ((strcmp(argv[4],"-FULL") == 0))
						{
							strcpy(ruta, argv[3]);
							codigoError = opcionTexto(hash, TRUE, ruta); //es FULL
						}
						else
						{
							codigoError = ERROR_FORMATO;
							opcionHelp();
						}
					}
				else
					{
						strcpy(ruta, argv[3]);
						codigoError = opcionTexto(hash, FALSE, ruta); //no es FULL
					}
			}
			else
				codigoError = opcionTexto(hash, FALSE, ruta); //stdout, no es FULL
			free (ruta);
		}

		if (strcmp(opcion, "-R") == 0){
			//printf("Ingrese la cantidad de elementos random a agregar (6 digitos maximo):\n");
			if (argc >= 4)
			{
				char cantidad[256];
				strcpy(cantidad, argv[3]);
				cantidad[6] = '\0';
				int i = 0;
				enum TBOOL formato = TRUE;
				while(cantidad[i] != '\0')
				{
					if(!validarNumero(cantidad[i]))
					{
						codigoError = ERROR_FORMATO;
						opcionHelp();
						formato = FALSE;
					}
					i++;
				}
				if (formato == TRUE)
				{
					int cant = atoi(cantidad);
					int j;
					for(j = 0; j < cant; j++){
						unsigned int clave = rand() % 99999 + 1;//insertar elementos random
						char * elemRandom = numeroToString(clave);
						InsertarElemento(hash, elemRandom, elemRandom, strlen(elemRandom) + 1);
						free(elemRandom);
					}
					codigoError = opcionTexto(hash, FALSE, "stdout"); //muestro el resultado
				}
			}
			else
			{
				codigoError = ERROR_ARGS_INSUF;
			}

		}

		/*Si se muestra la ayuda, se finaliza el programa*/
		if (strcmp(opcion, "-B") == 0)
		{
			entrada = leerEntrada();
			if(entrada == NULL){
				codigoError = ERROR_ARCHIVO_VACIO;
				mostrarError(codigoError);
				CerrarHashExtensible(hash);
				return OK;
			}
			actual = obtenerArgumento (entrada, &posicion);
			if(actual == NULL)
			{
				codigoError = ERROR_ARCHIVO_ARGUMENTOS;
				mostrarError(codigoError);
				free(entrada);
				CerrarHashExtensible(hash);
				return OK;
			}
			while (actual != NULL)
			{
				codigoError = opcionBuscar(actual, hash);
				free(actual);
				if(codigoError != OK)
					mostrarError(codigoError);
				actual = obtenerArgumento(entrada, &posicion);
			}
			free(entrada);

		}else if (strcmp(opcion, "-I") == 0){
			entrada = leerEntrada();
			if(entrada == NULL){
				codigoError = ERROR_ARCHIVO_VACIO;
				mostrarError(codigoError);
				CerrarHashExtensible(hash);
				return OK;
			}
			actual = obtenerArgumento(entrada, &posicion);
			if(actual == NULL)
			{
				codigoError = ERROR_ARCHIVO_ARGUMENTOS;
				mostrarError(codigoError);
				free(entrada);
				CerrarHashExtensible(hash);
				return OK;
			}
			while(actual != NULL)
			{
				codigoError = opcionInsertar(actual, hash);
				free(actual);
				if(codigoError != OK)
					mostrarError(codigoError);
				actual = obtenerArgumento(entrada, &posicion);
			}
			free(entrada);

		}else if (strcmp(opcion, "-M") == 0){
			entrada = leerEntrada();
			if(entrada == NULL){
				codigoError = ERROR_ARCHIVO_VACIO;
				mostrarError(codigoError);
				CerrarHashExtensible(hash);
				return OK;
			}
			actual = obtenerArgumento(entrada, &posicion);
			if(actual == NULL)
			{
				codigoError = ERROR_ARCHIVO_ARGUMENTOS;
				mostrarError(codigoError);
				free(entrada);
				CerrarHashExtensible(hash);
				return OK;
			}
			while(actual != NULL)
			{
				codigoError = opcionModificar(actual, hash);
				free(actual);
				if(codigoError != OK)
					mostrarError(codigoError);
				actual = obtenerArgumento(entrada, &posicion);
			}
			free(entrada);

		}else if (strcmp(opcion, "-Q") == 0){
			entrada = leerEntrada();
			if(entrada == NULL){
				codigoError = ERROR_ARCHIVO_VACIO;
				mostrarError(codigoError);
				CerrarHashExtensible(hash);
				return OK;
			}
			actual = obtenerArgumento(entrada, &posicion);
			if(actual == NULL)
			{
				codigoError = ERROR_ARCHIVO_ARGUMENTOS;
				mostrarError(codigoError);
				CerrarHashExtensible(hash);
				free(entrada);
				return OK;
			}
			while (actual != NULL)
			{
				codigoError = opcionQuitar(actual, hash);
				free(actual);
				if(codigoError != OK)
					mostrarError(codigoError);
				actual = obtenerArgumento(entrada, &posicion);
			}
			free(entrada);
		}
	}else{
		codigoError = ERROR_OPERACION_INVALIDA;
		mostrarError(codigoError);
		opcionHelp();
	}


	CerrarHashExtensible(hash);
	//Se guarda el estado final de la tabla

	return OK;
}

/******* FIN MAIN *******/

char* leerEntrada () //Levanta todo el contenido de stdin
{

   	char c = fgetc(stdin); //lee desde entrada standard

   	if(c == EOF) return NULL; //vacío

   	char * argumentos = (char*) malloc (sizeof(char) * 2);

	memset(argumentos,0,2);

	int i = 0;

	while (c != EOF)
	{
		argumentos[i] = c;
		argumentos = (char *) realloc (argumentos, strlen(argumentos) +2);
		i++;
		c = fgetc(stdin);
		argumentos[i] = '\0';
	}
   	return argumentos;
}

char* obtenerArgumento(char* entrada, int* posicion)
{
	char * inicioBusqueda;

	char * inicio = NULL;
	char * fin = NULL;
	char * retorno;

	unsigned int longitud = 0;

	if (*posicion > strlen(entrada)) return NULL;

	inicioBusqueda = *posicion * sizeof(char) + entrada;

    inicio = strchr(inicioBusqueda,'(');

    if (inicio == NULL ) return NULL;

    fin = strchr(inicio,')');

    if (fin == NULL) return NULL;

    longitud = fin - inicio + 1 ;

    retorno = (char *) malloc(sizeof(char) * (longitud+1));

    memcpy(retorno,inicio,longitud);

    retorno[longitud] = FIN_CADENA;

    (*posicion)= fin - entrada + 1;

    return retorno;
}


void opcionHelp(){
	printf ("Estas ordenes de consola se encuentran definidas internamente.\n\n");
	printf ("archivo -I\t\t\tInsertar un elemento. Formato: (clave;valor)\n");
	printf ("archivo -B\t\t\tBuscar un elemento a partir de su clave. Formato: (clave;)\n");
	printf ("archivo -M\t\t\tModificar un elemento. Formato: (clave;valor)\n");
	printf ("archivo -Q\t\t\tQuitar un elemento. Formato: (clave;)\n");
	printf ("archivo -R cantidadAgregar\tAgregar elementos al azar\n");
	printf ("archivo -S\t\t\tVuelca la estructura en pantalla.\n");
	printf ("           archivoDestino\tVuelca la estructura en archivoDestino.\n");
	printf ("           -FULL\t\tVuelca la estructura mas los datos en pantalla.\n");
	printf ("           -FULL archivoDestino\tVuelca la estructura mas los datos en archivoDestino.\n");
	printf ("-h\t\t\t\tImprime la ayuda.\n");

}

int esOpcionValida(char* opcion){
	int esValida = 0;
	if(strcmp(opcion,"-B") == 0 ||
		strcmp(opcion,"-I") == 0 ||
		strcmp(opcion,"-M") == 0 ||
		strcmp(opcion,"-Q") == 0 ||
		strcmp(opcion,"-S") == 0 ||
		strcmp(opcion,"-R") == 0
		)
		esValida = 1;
	return esValida;
}

void mostrarError(int codigo)
{
	switch(codigo){
	case ERROR: 			printf ("\nERROR: No se pudo realizar la operacion (es posible que la clave este repetida).\n\n");
	break;
	case ERROR_ARGS_INSUF: 	printf ("\nERROR: Cantidad de argumentos insuficientes.\n\n");
	break;
	case ERROR_FORMATO: 	printf ("\nERROR: Formato no correspondiente.\n\n");
	break;
	case ERROR_CREACION: 	printf ("\nERROR: No se pudo crear el Hashing.\n\n");
	break;
	case ERROR_ARCHIVO_VACIO: 	printf ("\nERROR: El archivo esta vacio\n\n");
	break;
	case ERROR_ARCHIVO_ARGUMENTOS: 	printf ("\nERROR: El archivo no contiene argumentos\n\n");
	break;
	case ERROR_OPERACION_INVALIDA: 	printf ("\nERROR: Se ha ingresado una operacion no valida.\n\n");
	break;
	case ERROR_CLAVE_NO_EXISTE:  printf ("\nERROR: No se pudo realizar la operacion (es posible que la clave no exista).\n\n");
	break;
	}
}

int validarParentesisAbre(char argumento){
	if(argumento == '(')
		return 1;
	else
		return 0;
}

int validarParentesisCierra(char argumento){
	if(argumento == ')')
		return 1;
	else
		return 0;
}

int validarNumero(char numero){
	int ascii = numero - '0';
	if((ascii<10)&&(ascii>-1))
		return 1;
	else
		return 0;
}

int obtenerClave(char* cadena, char** clave, int* posicion)
{
	if (validarParentesisAbre(cadena[*posicion]))
	{
		(*posicion)++; /*primero viene el '(' */

		int i = 0;
		char copia[100];
		char * esFin = cadena + (*posicion);

		while ((cadena[*posicion] != ';') && (strlen(esFin) > 0)) //no llegue a fin de lectura
		{
			if (i < 99) //lee hasta 99 caracteres, luego sigue hasta el primer ';' ignorando lo del medio
			{
				copia[i] = cadena[*posicion];
				i++;
			}
			(*posicion)++;
			esFin = cadena + (*posicion);
		}
		if(cadena[*posicion] == ';')
		{
			copia[i] = FIN_CADENA;
			(*clave) = (char*) malloc(sizeof(char)*(strlen(copia)+1));
			strcpy(*clave, copia);
		}else return ERROR_FORMATO;
	}
	else
		return ERROR_FORMATO;

	return OK;
}

int obtenerValor(char* cadena,char** valor,int* posicion)
{
	int tamanio = 0;
	int comienzo = *posicion;
	while((cadena[*posicion] != ')') && (cadena[*posicion] != FIN_CADENA))
	{
		tamanio++;
		(*posicion)++;
	}
	if (!validarParentesisCierra(cadena[*posicion]))
		return ERROR_FORMATO;

	*valor = (char*) malloc (sizeof(char) * tamanio + 1);
	memcpy(*valor, cadena + comienzo, tamanio);
	(*valor)[tamanio] = FIN_CADENA;
	return OK;
}

int opcionBuscar(char* cadena, HashExtensible* hash)
{
	char* clave = NULL;
	int posicion = 0;
	obtenerClave(cadena, &clave, &posicion);
	posicion++;
	if(!validarParentesisCierra(cadena[posicion]))
	{
		free(clave);
		return ERROR_FORMATO;
	}
	void* buscado = ObtenerElemento (hash, clave);
	if(buscado)
	{
		printf("El elemento recuperado es: '%s'\n", (char*)buscado);
		free(buscado);
	}
	else
		printf("No se encontro el elemento  asociado a la clave %s\n", clave);
	free(clave);
	return OK;
}

int opcionInsertar(char* cadena, HashExtensible* hash)
{
	char* clave = NULL;
	char* valor;
	int posicion = 0;
	int error = obtenerClave(cadena,&clave,&posicion);
	if(error)
		return error;
	posicion++;
	error = obtenerValor(cadena, &valor, &posicion);
	if(error){
		free(clave);
		free(valor);
		return error;
	}
	int resultado = InsertarElemento(hash, clave, valor, strlen(valor) + 1); //se tiene en cuenta el '\0'
	if(resultado == OK)
		printf("Se inserto el elemento '%s' con clave %s\n", valor, clave);
	else
		printf("No se pudo insertar el elemento '%s' con clave %s\n", valor, clave);
	free(clave);
	free(valor);
	return resultado;
}

int opcionModificar(char* cadena, HashExtensible* hash){
	char* clave = NULL;
	char* valor;
	int posicion = 0;
	int error = obtenerClave(cadena,&clave,&posicion);
	if(error)
	{
		free(clave);
		return error;
	}
	posicion++;
	error = obtenerValor(cadena, &valor, &posicion);
	if(error){
		free(clave);
		free(valor);
		return error;
	}
	int resultado = ModificarElemento(hash, clave, valor, strlen(valor) + 1); //se tiene en cuenta el '\0'
	if(resultado == OK)
		printf("Se modifico el elemento asociado a la clave %s con el valor '%s'\n", clave, valor);
	else
	{
		printf("No se pudo modificar el elemento asociado a la clave %s\n", clave);
		resultado = ERROR_CLAVE_NO_EXISTE;
	}

	free(clave);
	free(valor);
	return resultado;
}

int opcionQuitar(char* cadena, HashExtensible* hash)
{
	char* clave = NULL;
	int posicion = 0;
	int error = obtenerClave(cadena, &clave, &posicion);
	if (error)
	{
		free(clave);
		return error;
	}
	posicion++;

	if(!validarParentesisCierra(cadena[posicion]))
		return ERROR_FORMATO;
	error = EliminarElemento (hash,clave);
	if (error == OK)
		printf("Se elimino el elemento asociado a la clave %s\n",clave);
	else
	{
		printf("No se pudo eliminar el elemento asociado a la clave %s\n",clave);
		error = ERROR_CLAVE_NO_EXISTE;
	}

	free(clave);
	return error;
}

int opcionTexto(HashExtensible* hash, enum TBOOL isFull, char* ruta)
{
	if(strcmp(ruta,"stdout") == 0)
		visualizar(hash, isFull, stdout);
	else
	{
		FILE * archSalida = fopen (ruta, "w+");
		visualizar (hash, isFull, archSalida);
		fclose(archSalida);
	}
	return OK;
}


char * numeroToString(unsigned int numero)
{
	//Obtengo la cantidad de digitos que tiene el numero
	unsigned int temp;
	unsigned int cantidadDigitos = 0;
	char * numeroS = NULL;

	temp=numero;

	while (temp > 0) {
			temp/=10;
			cantidadDigitos++;
	}
	//Paso de int a chr
	numeroS = (char*) malloc (sizeof(char)* (cantidadDigitos+1));
	sprintf(numeroS,"%i",numero);
	numeroS[cantidadDigitos]='\0';

	return numeroS;
}
